computersfandomcom_he-20200214-history
שפת C/מערכים
מערך הוא מבנה נתונים שמאפשר שמירה של משתנים רבים מאותו טיפוס תחת אותו שם, כאשר כל אחד מהמשתנים מקבל מספר מזהה ייחודי. שימוש במערכים מאפשר עבודה נוחה עם מידע שמורכב מחלקים רבים הזהים זה לזה. מהו מערך? בהסבר מהם משתנים בפרק משתנים, עמדנו על כך שאפשר לחשוב על משתנה כ"תיבה", שאפשר לשים בה ערכים. באופן דומה, אפשר לחשוב על מערך כעל "שורת תיבות". לשורת התיבות יש שם (באופן דומה לכך שלמשתנה יש שם), ואפשר לגשת לתיבה ספיציפית על ידי ציון מספר התיבה בשורה. בתרשים הבא, לדוגמה, מוצג המשתנה grade, שהוא כעין תיבה (המורכבת משני בתים), ובתוכה כעת המספר 80. בתרשים גם מוצג המערך grades, מערך באורך ארבע, שהוא כעין שורה של ארבע תיבות (שכל אחת מהן מורכבת משני בתים), ובתיבות כעת המספרים 90, 80, 56, ו100. מרכז|100%|משתנה ומערך הגדרת מערך הגדרת מערך צריכה להיות מהצורה: []; כאשר type הוא סוג המשתנה של אברי המערך, name הוא השם שאנו בוחרים למערך כולו, וsize (שחייב להיות שלם חיובי) הוא מספר האיברים שמכיל המערך. לדוגמה, נתבונן בקטע הקוד הבא: int array130; char array250; double array31; # השורה הראשונה מכריזה על מערך שלמים array1 בעל 30 איברים. # השורה השניה מכריזה על מערך תווים array2 בעל 50 מקומות. # השורה השלישית מכריזה על מערך מספרי נקודה צפה array3 בעל איבר יחיד. גישה לאברי מערך כדי לגשת לאיבר במערך, אפשר להשתמש ב''אינדקס'': יש לכתוב את שם המערך ואחריו בתוך סוגריים מרובעים את המספר הסידורי של האיבר במערך: array12 = 5; בדוגמא זו, המשתנה שהאינדקס שלו הוא 2 במערך array1 שהוגדר קודם מקבל את הערך 5. בשפת C נהוג כי האינדקס הראשון במערך הוא 0 ולא 1. כלומר, array10 הוא האיבר הראשון במערך. כתוצאה מכך, האיבר האחרון במערך בגודל n הוא בעל המספר הסידורי . אם נכתוב array130 נחרוג בכך מגבולות המערך, כי האיבר מס' 29 הוא האיבר האחרון בו. יתרונות מערכים על פני משתנים רבים נתחיל במספר משימות פשוטות לשימוש במערכים. מיד בהמשך נשתמש בדוגמאות כדי להבין את הייתרונות שבשימוש במערכים. הדוגמאות הפשוטות שראינו מספיקות כדי להבין את יתרונות המערכים. להלן תוכנית הקולטת 10 ערכים ומדפיסה אותם בסדר הפוך, שאינה משתמשת במערכים: int main() { int num0, num1, num2, num3, num4, num5, num6, num7, num8, num9; scanf("%d",&num0); scanf("%d",&num1); scanf("%d",&num2); scanf("%d",&num3); scanf("%d",&num4); scanf("%d",&num5); scanf("%d",&num6); scanf("%d",&num7); scanf("%d",&num8); scanf("%d",&num9); printf("%d\n",num9); printf("%d\n",num8); printf("%d\n",num7); printf("%d\n",num6); printf("%d\n",num5); printf("%d\n",num4); printf("%d\n",num3); printf("%d\n",num2); printf("%d\n",num1); printf("%d\n",num0); return 0; } נוכל לראות יתרונות בולטים לגרסה המשתמשת במערכים: *במקום להשתמש בעשרה משתנים שונים די להגדיר משתנה יחיד, תחת השם numbers. **אם התוכנית הייתה צריכה לעבוד עם מספר גדול מספיק של של משתנים, נניח 100, לא היה מעשי להגדיר משתנה לכל אחד מהם. **לעתים לא יודעים מראש את מספר המשתנים (כמו, לדוגמה, המקרה שבו גם מספר זה מתקבל בקלט). הפועל היוצא הוא שלא היינו יודעים כמה משתנים להגדיר. עם זאת, כשנגיע לניהול זיכרון דינאמי, נראה שאפשר להקצות מערכים בגדלים הנקבעים בזמן הריצה (כמו מקלט, לדוגמה). *הקוד המשתמש בלולאות קצר יותר. כך לדוגמה, הדפסת האיברים על ידי לולאה (2 שורות), קצרה יותר מהדפסתם בצורה פרטנית (10 שורות). אתחול מערך לעתים, כאשר מייצרים מערך, יש לתת ערך התחלתי לאיבריו. לדוגמה, נניח שמייצרים מערך של שלמים בגודל 3, ורוצים להכניס לו את האיברים 12, 22, ו33. כפי שראינו בגישה לאברי מערך, אפשר לגשת לכל אחד מאיברי המערך. נוכל, לכן, להשתמש בהשמה לכל אחד מאיבריו: int nums3; nums0 = 12; nums1 = 22; nums2 = 33; עם זאת, לצורך אתחול המערך בלבד, השפה מאפשרת צורה מקוצרת יותר: int nums3 = {12, 22, 33}; מחרוזות - מערכים של תווים אחד השימושים הנפוצים של מערכים הוא לשמירת מילים או משפטים על ידי מחרוזות, כלומר מערכים של תווים. שפת C כוללת פונקציות עזר רבות למחרוזות. תוכל לקרוא על כך כאן. זהירות בטיפול במערכים שימוש שגוי במערכים עלול לגרום לתוצאות קשות. גלישה מגבולות המערך נתבונן בקטע הקוד הבא: /* An array of 3 places. */ int nums3; /* This is an error: the array does not have 10 places. */ num10 = 12; הקוד ניגש למערך בתא 10, למרות שתא זה כלל אינו חלק מהמערך. זוהי שגיאה, כמובן. השלכות הגלישה ממערך מה תהיה השפעת קוד זה? הדבר תלוי במערכת בה הקוד רץ, אולם היא יכולה להיות חמורה מאד. ייתכן שבמחשב אחד תופיע בעיה קלה יחסית, אך במחשב אחר (או באותו מחשב בזמן אחר) תופיע בעיה חמורה הרבה יותר. כדי להבין את חומרת הסכנה הטמונה בשגיאות גלישה, כדאי לדעת שיש קבוצת התקפות עוינות על מחשבים, הידועה בשם buffer overrun, המבוססת בדיוק על חוסר עירנות של מתכנתים בנקודה זו. מערכים בגודל לא-ידוע מראש ביתרונות מערכים על פני משתנים רבים ראינו תוכנית שקולטת מספרים ומדפיסה אותם בסדר הפוך לסדר קליטתם. התוכנית הניחה שהמשתמש רוצה להפוך בדיוק 10 מספרים. במהדרים חדשים יחסית, קל לשנות את התוכנית כך שמספר המספרים שהמשתמש רוצה להפוך - גם הוא יהיה חלק מהקלט: #include /* Queries the user for how many numbers she wants reversed. */ int find_how_many_numbers(); int main() { int num_numbers = find_how_many_numbers(); int numbersnum_numbers; int i; for(i = 0; i < num_numbers; i++) scanf("%d",&numbersi); for(i = num_numbers - 1; i >= 0; i--) printf("%d\n",numbersi); return 0; } int find_how_many_numbers() { int num_numbers; printf("Please enter how many numbers to reverse: "); scanf("%d", &num_numbers); return num_numbers; } בקוד זה, חשוב לשים לב לשורות הבאות: int num_numbers = find_how_many_numbers(); int numbersnum_numbers; השורה הראשונה מאתחלת את num_numbers לערך המוחזר מfind_how_many_nubers (שהיא פונקציה שמבקשת מהמשתמש להקליד את מספר המספרים שברצונו להפוך, ומחזירה את המספר שהקליד). השורה השניה מייצרת מערך בגודל מספר זה. מערכים רב ממדיים ניתן לחשוב על מערך כשורה המכילה מספר תאים, כאשר כל תא הוא תו, שלם, או מספר נקודה צפה. אפשר אפילו להגדיר מערך שבו כל תא הוא מערך. מערך רב מימדי הוא מערך של מערכים. להלן דוגמה למערך דו-מימדי: int matrix_2d103; זהו מערך של 10 מערכים, כל אחד בעלי 3 איברים. כדי לגשת לאיבר השני של המערך השלישי, נרשום /* Access to the second element of the third array. */ matrix_2d21 = 2; גם כאן, כמובן, יש להיזהר מגלישה: /* Error! out of range */ matrix_2d210 = 2; אין מניעה לעצור בהכרח בשני מימדים. אפשר להגדיר מערך בעל מספר שרירותי של מימדים. להלן דוגמה למערך בעל שישה מימדים: /* A 6 dimensional array. */ int matrix_6d582931; מערכים